// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <iostream>
#include <vector>
#include <fstream>
#include <string>

#include "Platform/FileName.h"
#include "Platform/FilePath.h"
#include "Platform/DynamicLibrary.h"
#include "Platform/TempDirectory.h"

#include "Option.h"
#include "Interfaces.h"
#include "Config.h"

using namespace Platform;

namespace CangjieMigrator {

std::vector<std::string> ReadVersionList(std::string name)
{
    std::ifstream file(Config::ExecutableDir / name);
    std::vector<std::string> versionList;

    if (!file) {
        std::cerr << "Open version_list failed." << std::endl;
        return versionList;
    }

    std::string line;
    while (std::getline(file, line)) {
        // 去除行首尾的空白字符
        size_t start = line.find_first_not_of(" \t\n\r");
        size_t end = line.find_last_not_of(" \t\n\r");
        if (start != std::string::npos) {
            line = line.substr(start, end - start + 1);
            versionList.push_back(line);
        }
    }

    file.close();
    return versionList;
}

bool Copy(const Path &from, const Path &to, bool fileMode)
{
    constexpr auto override = FileSystem::copy_options::overwrite_existing;
    constexpr auto recursive = FileSystem::copy_options::recursive;

    if (fileMode) {
        if (!copy_file(from, to, override)) {
            std::cerr << "Copy file " << from << " to " << to << " failed." << std::endl;
            return false;
        }
        return true;
    }

    try {
        copy(from, to, override | recursive);
    } catch (std::exception &e) {
        std::cerr << "Copy directory " << from << " to " << to << " throw exception " << e.what() << std::endl;
        return false;
    }
    return true;
}

bool ApplyVersionMigration(const std::string &version, const UpdaterOption &opts)
{
    const auto dynamicLibPath = Config::GetBinDependencyDir(version);
    try {
        auto lsp = DynamicLibrary(FilePath::GetCangjieLspPath(Config::CangjieHome));
        auto cjm = DynamicLibrary(dynamicLibPath / FileName::Cjm);

        auto getVersion = cjm.GetSymbol<GetVersionFunc>("UpdaterGetVersion");
        if (getVersion == nullptr) {
            return false;
        }

        auto apply = cjm.GetSymbol<ApplyFunc>("UpdaterApply");
        if (apply == nullptr) {
            return false;
        }

        ApplyReturnCode ret = apply(&opts);
        if (ret != 0) {
            return false;
        }
    } catch (DynamicLibrary::OpenFailed &e) {
        std::cerr << e.ToString() << std::endl;
        return false;
    } catch (std::exception &e) {
        std::cerr << "When open version " << version << " throws exception: " << e.what() << std::endl;
        return false;
    }
    return true;
}

}  // namespace CangjieMigrator

using namespace CangjieMigrator;

using FileSystem::temp_directory_path;

static bool UpgradeFile(const Option &option, const std::vector<std::string> &versionList)
{
    std::string fileName = option.outputPath.filename().string();
    const auto temp = TempDirectory();
    auto currentSrcPath = option.inputPath;
    for (const auto &version : versionList) {
        auto tempPath = temp / version;

        std::string cangjieHomeString = Config::CangjieHome.string();
        std::string versionString = "0.53.18";
        std::string sourceString = currentSrcPath.string();
        std::string targetString = tempPath.string();
        std::string importPathString = "";

        auto updaterOption = UpdaterOption {
            cangjieHomeString.c_str(),
            versionString.c_str(),
            sourceString.c_str(),
            targetString.c_str(),
            importPathString.c_str(),
            option.GetUpdaterMode()
        };
        if (!ApplyVersionMigration(version, updaterOption)) {
            std::cerr << "Migration failed for version: " << version << std::endl;
            return false;
        }

        currentSrcPath = tempPath;
    }

    if (!Copy(currentSrcPath, option.outputPath, option.IsFileMode())) {
        std::cerr << "Failed to copy to final output " << option.outputPath << std::endl;
        return false;
    }

    return true;
}

static bool UpgradeProject(const Option &option, const std::vector<std::string> &versionList)
{
    const auto temp = TempDirectory();
    auto currentSrcPath = option.inputPath;
    for (const auto &version : versionList) {
        auto tempPath = temp / version;

        std::string cangjieHomeString = Config::CangjieHome.string();
        std::string versionString = "0.53.18";
        std::string sourceString = currentSrcPath.string();
        std::string targetString = tempPath.string();
        std::string importPathString = "";

        auto updaterOption = UpdaterOption {
            cangjieHomeString.c_str(),
            versionString.c_str(),
            sourceString.c_str(),
            targetString.c_str(),
            importPathString.c_str(),
            option.GetUpdaterMode()
        };
        if (!ApplyVersionMigration(version, updaterOption)) {
            std::cerr << "Migration failed for version: " << version << std::endl;
            return false;
        }

        currentSrcPath = tempPath;
    }

    if (!Copy(currentSrcPath, option.outputPath, option.IsFileMode())) {
        std::cerr << "Failed to copy to final output " << option.outputPath << std::endl;
        return false;
    }

    return true;
}

static bool UpgradeDeveco(const Option &option, const std::vector<std::string> &versionList)
{
    std::string versionString = "0.53.18";
    std::string sourceString = option.inputPath.string();
    std::string targetString = option.outputPath.string();
    std::string cangjieHomeString = "";
    std::string importPathString = "";

    auto updaterOption = UpdaterOption {
        cangjieHomeString.c_str(),
        versionString.c_str(),
        sourceString.c_str(),
        targetString.c_str(),
        importPathString.c_str(),
        option.GetUpdaterMode()
    };
    if (!ApplyVersionMigration(versionString, updaterOption)) {
        std::cerr << "Migration failed for version: " << "5.0" << std::endl;
        return false;
    }
    return true;
}

static int RunOnFile(const Option &option)
{
    // 1. Prepare environment
    if (!Config::PrepareEnvironment()) {
        return 1;
    }

    // 2. Read support version list
    const auto versionList = ReadVersionList("version_list");
    if (versionList.empty()) {
        std::cerr << "No versions to migrate." << std::endl;
        return 1;
    }

    // 3. Apply upgrade in version list
    if (!UpgradeFile(option, versionList)) {
        return 1;
    }

    return 0;
}

static int RunOnProject(const Option &option)
{
    // 1. Prepare environment
    if (!Config::PrepareEnvironment()) {
        return 1;
    }

    // 2. Read support version list
    const auto versionList = ReadVersionList("version_list");
    if (versionList.empty()) {
        std::cerr << "No versions to migrate." << std::endl;
        return 1;
    }

    // 3. Apply upgrade in version list
    if (!UpgradeProject(option, versionList)) {
        return 1;
    }

    return 0;
}

static int RunOnDeveco(const Option &option)
{
    if (!Config::PrepareEnvironment()) {
        return 1;
    }

    // 1. Read support version list
    const auto versionList = ReadVersionList("version_list_deveco");
    if (versionList.empty()) {
        std::cerr << "No versions to migrate." << std::endl;
        return 1;
    }

    // 2. Apply upgrade in version list
    if (!UpgradeDeveco(option, versionList)) {
        return 1;
    }

    return 0;
}

int main(const int argc, const ArgvType argv)
{
    Option option;
    if (!option.ParseArgs(argc, argv)) {
        std::cerr << "Parse arg error." << std::endl;
        return 1;
    }
    if (option.IsHelp()) {
        return 0;
    }
    std::cout << option.ToString() << std::endl;

    if (option.IsDevecoMode()) {
        return RunOnDeveco(option);
    } else if (option.IsFileMode()) {
        return RunOnProject(option);
    } else { // FileMode
        return RunOnFile(option);
    }
}
